"""
认证相关的路由处理模块

该模块处理所有与用户认证相关的请求，包括：
- 用户登录和注册
- 令牌管理和刷新
- 验证码发送和验证
- 密码重置
- 用户信息获取
"""

import re
import jwt
import uuid
import os
from dotenv import load_dotenv

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from datetime import datetime, timedelta, UTC

from schemas.generated.auth import *
from models.user import User
from dao.postgresql_connect import SessionLocal
from dao.alibabacloud import AlibabaCloudSMS
from dao.redis_connect import RedisClient
from utils.verify import send_verification_code as send_code, verify_code as verify_code_util
from utils import verify_password, get_password_hash

# 加载环境变量
load_dotenv()

auth_router = APIRouter()

# JWT配置
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
if not SECRET_KEY:
    raise ValueError("JWT_SECRET_KEY 环境变量未设置")
ALGORITHM = "HS256"
# 访问令牌过期时间 / 分钟
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 刷新令牌过期时间 / 天
REFRESH_TOKEN_EXPIRE_DAYS = 7

# OAuth2配置
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# Redis key前缀
TOKEN_BLACKLIST_PREFIX = "token:blacklist:"
REFRESH_TOKEN_PREFIX = "refresh_token:"


def create_access_token(data: dict) -> str:
    """
    创建访问令牌
    
    Args:
        data: 要编码的数据，包含用户信息
        
    Returns:
        str: JWT访问令牌
    """
    to_encode = data.copy()
    expire = datetime.now(UTC) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({
        "exp": expire,
        "role": data.get("role", "user")  # 添加角色信息，默认为普通用户
    })
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)


def create_refresh_token(data: dict) -> str:
    """
    创建刷新令牌
    
    Args:
        data: 要编码的数据
        
    Returns:
        str: JWT刷新令牌
    """
    to_encode = data.copy()
    expire = datetime.now(UTC) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    to_encode.update({
        "exp": expire,
        "role": data.get("role", "user")  # 添加角色信息，默认为普通用户
    })
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)


def verify_refresh_token(token: str) -> dict:
    """
    验证刷新令牌
    
    Args:
        token: 刷新令牌
        
    Returns:
        dict: 解码后的令牌数据
        
    Raises:
        HTTPException: 令牌无效或过期时抛出
    """
    try:
        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="刷新令牌已过期",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except jwt.JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="无效的刷新令牌",
            headers={"WWW-Authenticate": "Bearer"},
        )


def validate_phone(phone: str) -> bool:
    """
    验证手机号格式
    
    Args:
        phone: 手机号
        
    Returns:
        bool: 手机号格式是否正确
    """
    pattern = r'^1[3-9]\d{9}$'  # 中国大陆手机号格式
    return bool(re.match(pattern, phone))


# 依赖项
def get_db():
    """
    获取数据库会话的依赖项
    
    Yields:
        Session: 数据库会话
    """
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User:
    """
    获取当前用户的依赖项
    
    Args:
        token: 访问令牌
        db: 数据库会话
        
    Returns:
        User: 当前用户对象
        
    Raises:
        HTTPException: 令牌无效或过期时抛出
    """
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无效的认证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        # 检查token是否在黑名单中
        redis_client = RedisClient.get_client()
        if redis_client.exists(f"{TOKEN_BLACKLIST_PREFIX}{token}"):
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="token已失效",
                headers={"WWW-Authenticate": "Bearer"},
            )

        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="token已过期",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except jwt.JWTError:
        raise credentials_exception

    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception
    return user


# 认证相关接口
@auth_router.post("/auth/register", response_model=MessageResponse)
async def register(request: RegisterRequest, db: Session = Depends(get_db)):
    """
    用户注册接口
    
    Args:
        request: 注册请求数据
        db: 数据库会话
        
    Returns:
        MessageResponse: 注册结果消息
        
    Raises:
        HTTPException: 注册失败时抛出
    """
    # 验证手机号格式
    if not validate_phone(request.phone):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="无效的手机号格式"
        )

    # 验证验证码
    if not verify_code_util(request.phone, request.verification_code):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="验证码错误或已过期"
        )

    # 检查用户名是否已存在
    if db.query(User).filter(User.username == request.username).first():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="用户名已存在"
        )

    # 检查手机号是否已存在
    if db.query(User).filter(User.phone == request.phone).first():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="手机号已被注册"
        )

    # 创建新用户，使用安全的密码哈希
    new_user = User(
        username=request.username,
        password=get_password_hash(request.password),
        phone=request.phone
    )

    db.add(new_user)
    db.commit()
    db.refresh(new_user)

    return MessageResponse(message="注册成功")


@auth_router.post("/auth/login", response_model=LoginResponse)
async def login(request: LoginRequest, db: Session = Depends(get_db)):
    """
    用户登录接口
    
    Args:
        request: 登录请求数据
        db: 数据库会话
        
    Returns:
        LoginResponse: 登录结果，包含访问令牌和刷新令牌
        
    Raises:
        HTTPException: 登录失败时抛出
    """
    user = None

    # 判断是手机号还是用户名
    if validate_phone(request.username_or_phone):
        user = db.query(User).filter(User.phone == request.username_or_phone).first()
    else:
        user = db.query(User).filter(User.username == request.username_or_phone).first()

    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户不存在"
        )

    # 验证码登录
    if request.verification_code:
        if not verify_code_util(user.phone, request.verification_code):
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="验证码错误"
            )
    # 密码登录
    elif request.password:
        if not verify_password(request.password, user.password):
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="密码错误"
            )
    else:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="需要提供密码或验证码"
        )

    # 创建访问令牌和刷新令牌
    access_token = create_access_token(data={
        "sub": user.username,
        "user_id": user.id,
        "phone": user.phone,
        "role": user.role.value
    })

    refresh_token = create_refresh_token(data={
        "sub": user.username,
        "user_id": user.id,
        "jti": str(uuid.uuid4()),  # 唯一标识符，用于撤销
        "role": user.role.value  # 添加角色信息
    })

    # 存储刷新令牌到Redis
    redis_client = RedisClient.get_client()
    redis_client.setex(
        f"{REFRESH_TOKEN_PREFIX}{refresh_token}",
        REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60,  # 转换为秒
        str(user.id)
    )

    # 更新最后登录时间
    user.last_login_at = datetime.now(UTC)
    db.commit()

    return LoginResponse(
        message="登录成功",
        access_token=access_token,
        refresh_token=refresh_token,
        user={
            "id": user.id,
            "username": user.username,
            "phone": user.phone,
            "email": user.email,
            "avatar": user.avatar,
            "role": user.role.value
        }
    )


@auth_router.post("/auth/logout", response_model=MessageResponse)
async def logout(request: LogoutRequest):
    """
    用户登出接口
    
    Args:
        request: 登出请求数据，包含访问令牌和刷新令牌
        
    Returns:
        MessageResponse: 登出结果消息
        
    Raises:
        HTTPException: 登出失败时抛出
    """
    try:
        redis_client = RedisClient.get_client()

        # 将访问令牌添加到黑名单
        if request.access_token:
            payload = jwt.decode(request.access_token, SECRET_KEY, algorithms=[ALGORITHM])
            exp = payload.get("exp")
            if exp:
                remaining_time = exp - int(datetime.now(UTC).timestamp())
                if remaining_time > 0:
                    redis_client.setex(
                        f"{TOKEN_BLACKLIST_PREFIX}{request.access_token}",
                        remaining_time,
                        "1"
                    )

        # 清除刷新令牌
        if request.refresh_token:
            redis_client.delete(f"{REFRESH_TOKEN_PREFIX}{request.refresh_token}")

        return MessageResponse(message="登出成功")
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"登出失败: {str(e)}"
        )


# 令牌管理接口
@auth_router.post("/auth/refresh", response_model=RefreshTokenResponse)
async def refresh_token(request: RefreshTokenRequest):
    """
    刷新访问令牌接口
    
    Args:
        request: 刷新令牌请求数据
        
    Returns:
        RefreshTokenResponse: 新的访问令牌和刷新令牌
        
    Raises:
        HTTPException: 刷新失败时抛出
    """
    try:
        # 验证刷新令牌
        payload = verify_refresh_token(request.refresh_token)

        # 检查刷新令牌是否在Redis中
        redis_client = RedisClient.get_client()
        if not redis_client.exists(f"{REFRESH_TOKEN_PREFIX}{request.refresh_token}"):
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="刷新令牌已失效",
                headers={"WWW-Authenticate": "Bearer"},
            )

        # 获取用户信息
        db = SessionLocal()
        try:
            user = db.query(User).filter(User.username == payload["sub"]).first()
            if not user:
                raise HTTPException(
                    status_code=status.HTTP_401_UNAUTHORIZED,
                    detail="用户不存在",
                    headers={"WWW-Authenticate": "Bearer"},
                )

            # 创建新的访问令牌
            new_access_token = create_access_token(data={
                "sub": user.username,
                "user_id": user.id,
                "phone": user.phone,
                "role": user.role.value
            })

            # 可选：创建新的刷新令牌（令牌轮换）
            new_refresh_token = create_refresh_token(data={
                "sub": user.username,
                "user_id": user.id,
                "jti": str(uuid.uuid4()),
                "role": user.role.value  # 添加角色信息
            })

            # 存储新的刷新令牌
            redis_client.setex(
                f"{REFRESH_TOKEN_PREFIX}{new_refresh_token}",
                REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60,
                str(user.id)
            )

            # 删除旧的刷新令牌
            redis_client.delete(f"{REFRESH_TOKEN_PREFIX}{request.refresh_token}")

            return RefreshTokenResponse(
                access_token=new_access_token,
                refresh_token=new_refresh_token
            )
        finally:
            db.close()

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"刷新令牌失败: {str(e)}"
        )


@auth_router.get("/auth/verify_token", response_model=TokenVerifyResponse)
async def verify_token(current_user: User = Depends(get_current_user)):
    """
    验证访问令牌有效性接口
    
    Args:
        current_user: 当前用户对象
        
    Returns:
        TokenVerifyResponse: 令牌验证结果
        
    Raises:
        HTTPException: 令牌无效时抛出
    """
    return {
        "valid": True,
        "user": {
            "id": current_user.id,
            "username": current_user.username,
            "phone": current_user.phone,
            "email": current_user.email,
            "avatar": current_user.avatar,
            "role": current_user.role.value
        }
    }


# 验证码相关接口
@auth_router.post("/auth/send_verification_code", response_model=SendVerificationCodeResponse)
async def send_verification_code(request: SendVerificationCodeRequest):
    """
    发送验证码接口
    
    Args:
        request: 发送验证码请求，包含手机号和验证码类型
        
    Returns:
        SendVerificationCodeResponse: 发送结果，包含消息和有效期
        
    Raises:
        HTTPException: 发送失败时抛出
    """
    if not validate_phone(request.phone):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="无效的手机号格式"
        )

    # 根据类型设置不同的有效期
    expire_times = {
        Type.register: 300,  # 5分钟
        Type.login: 180,     # 3分钟
        Type.reset: 600      # 10分钟
    }
    expire_in = expire_times.get(request.type, 300)

    # 生成并存储验证码
    code = send_code(phone=request.phone, code_type=request.type.value, expire_in=expire_in)

    # 发送短信验证码
    sms_client = AlibabaCloudSMS()
    success = await sms_client.send_verification_code(
        phone_number=request.phone, 
        code=code,
        code_type=request.type.value
    )

    if not success:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="验证码发送失败"
        )

    return SendVerificationCodeResponse(
        message="验证码已发送",
        expire_in=expire_in
    )


@auth_router.post("/auth/verify_code", response_model=MessageResponse)
async def verify_code(request: VerificationCodeRequest):
    """
    验证验证码接口
    
    Args:
        request: 验证码验证请求数据
        
    Returns:
        MessageResponse: 验证结果消息
        
    Raises:
        HTTPException: 验证失败时抛出
    """
    if not validate_phone(request.phone):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="无效的手机号格式"
        )

    if not verify_code_util(request.phone, request.verification_code):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="验证码错误"
        )

    return MessageResponse(message="验证成功")


# 用户信息接口
@auth_router.get("/auth/me", response_model=UserResponse)
async def read_users_me(current_user: User = Depends(get_current_user)):
    """
    获取当前用户信息接口
    
    Args:
        current_user: 当前用户对象
        
    Returns:
        UserResponse: 用户信息
    """
    return {
        "id": current_user.id,
        "username": current_user.username,
        "phone": current_user.phone,
        "email": current_user.email,
        "avatar": current_user.avatar,
        "role": current_user.role.value
    }


# 密码管理接口
@auth_router.post("/auth/reset_password", response_model=MessageResponse)
async def reset_password(request: ResetPasswordRequest, db: Session = Depends(get_db)):
    """
    重置密码接口
    
    Args:
        request: 重置密码请求数据
        db: 数据库会话
        
    Returns:
        MessageResponse: 重置结果消息
        
    Raises:
        HTTPException: 重置失败时抛出
    """
    if not validate_phone(request.phone):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="无效的手机号格式"
        )

    # 验证验证码
    if not verify_code_util(request.phone, request.verification_code):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="验证码错误"
        )

    # 更新密码，使用安全的密码哈希
    user = db.query(User).filter(User.phone == request.phone).first()
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="用户不存在"
        )

    user.password = get_password_hash(request.new_password)
    db.commit()

    return MessageResponse(message="密码重置成功")


@auth_router.post("/auth/change_password", response_model=MessageResponse)
async def change_password(
    request: ChangePasswordRequest,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """
    修改密码接口
    
    Args:
        request: 修改密码请求数据
        current_user: 当前用户对象
        db: 数据库会话
        
    Returns:
        MessageResponse: 修改结果消息
        
    Raises:
        HTTPException: 修改失败时抛出
    """
    # 验证旧密码
    if not verify_password(request.old_password, current_user.password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="当前密码错误"
        )

    # 更新密码
    current_user.password = get_password_hash(request.new_password)
    db.commit()

    return MessageResponse(message="密码修改成功")
